package com.flipkart.android.proteus.value;

import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.InflateException;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.flipkart.android.proteus.ProteusContext;

import org.xmlpull.v1.XmlPullParserException;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;

public class VectorDrawable extends Drawable {

    private static final String LOGTAG = VectorDrawable.class.getSimpleName();
    private static final String SHAPE_SIZE = "size";
    private static final String SHAPE_VIEWPORT = "viewport";
    private static final String SHAPE_PATH = "path";
    private static final String SHAPE_VECTOR = "vector";
    private static final int LINECAP_BUTT = 0;
    private static final int LINECAP_ROUND = 1;
    private static final int LINECAP_SQUARE = 2;
    private static final int LINEJOIN_MITER = 0;
    private static final int LINEJOIN_ROUND = 1;
    private static final int LINEJOIN_BEVEL = 2;

    private final VectorDrawableState mVectorState;

    private int mAlpha = 0xFF;

    private ProteusContext mContext;

    public VectorDrawable() {
        mVectorState = new VectorDrawableState(null);
    }

    public VectorDrawable(ObjectValue value, ProteusContext context) {
        mVectorState = new VectorDrawableState(null);
        mContext = context;
    }

    @Override
    public void draw(@NonNull Canvas canvas) {
        canvas.drawColor(0xffFFFFFF);
        final int saveCount = canvas.save();
        final Rect bounds = getBounds();
        canvas.translate(bounds.left, bounds.top);
        mVectorState.mVPathRenderer.draw(canvas, bounds.width(), bounds.height());
        canvas.restoreToCount(saveCount);
    }

    @Override
    public void setAlpha(int i) {

    }

    @Override
    public void setColorFilter(@Nullable ColorFilter colorFilter) {

    }

    @Override
    public int getIntrinsicWidth() {
        return (int) mVectorState.mVPathRenderer.mBaseWidth;
    }
    @Override
    public int getIntrinsicHeight() {
        return (int) mVectorState.mVPathRenderer.mBaseHeight;
    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }

    @Nullable
    @Override
    public ConstantState getConstantState() {
        return mVectorState;
    }

    public void inflate(ObjectValue value) {
        VPathRenderer vPathRenderer = inflateInternal(value);
        setPathRenderer(vPathRenderer);
    }

    boolean noSizeTag = true;
    boolean noViewportTag = true;
    boolean noPathTag = true;

    private void parse(VGroup currentGroup, VPathRenderer renderer, ObjectValue value) {
        String name = value.getAsString("type");
        if (SHAPE_PATH.equals(name)) {
            final VPath path = new VPath();
            path.inflate(value);
            currentGroup.add(path);
            noPathTag = false;
        } else if (SHAPE_SIZE.equals(name)) {
            renderer.parseSize(value);
            noSizeTag = false;
        }

        Value children = value.get("children");
        if (children != null && children.isArray()) {
            for (int i = 0; i < children.getAsArray().size(); i++) {
                parse(currentGroup, renderer, children.getAsArray().get(i).getAsObject());
            }
        }
    }

    private VPathRenderer inflateInternal(ObjectValue value) {
        final VPathRenderer pathRenderer = new VPathRenderer();

        VGroup currentGroup = new VGroup();
        parse(currentGroup, pathRenderer, value);

        pathRenderer.mBaseWidth = Dimension.valueOf(value.get("width").getAsString())
                .apply(mContext);
        pathRenderer.mBaseHeight = Dimension.valueOf(value.get("height").getAsString())
                .apply(mContext);
        pathRenderer.mViewportWidth = Dimension.valueOf(value.get("viewportWidth").getAsString())
                .apply(mContext);
        pathRenderer.mViewportHeight = Dimension.valueOf(value.get("viewportHeight").getAsString())
                .apply(mContext);

        if (noPathTag) {
            final StringBuilder tag = new StringBuilder();
            if (noSizeTag) {
                tag.append(SHAPE_SIZE);
            }
            if (noViewportTag) {
                if (tag.length() > 0) {
                    tag.append(" & ");
                }
                tag.append(SHAPE_SIZE);
            }
            if (noPathTag) {
                if (tag.length() > 0) {
                    tag.append(" or ");
                }
                tag.append(SHAPE_PATH);
            }
            throw new InflateException("no " + tag + " defined");
        }

        pathRenderer.mCurrentGroup = currentGroup;
        pathRenderer.parseFinish();
        return pathRenderer;
    }

    private void setPathRenderer(VPathRenderer pathRenderer) {
        mVectorState.mVPathRenderer = pathRenderer;
    }

    private static class VectorDrawableState extends ConstantState {
        int mChangingConfigurations;
        VPathRenderer mVPathRenderer;
        Rect mPadding;
        public VectorDrawableState(VectorDrawableState copy) {
            if (copy != null) {
                mChangingConfigurations = copy.mChangingConfigurations;
                mVPathRenderer = new VPathRenderer(copy.mVPathRenderer);
                mPadding = new Rect(copy.mPadding);
            }
        }
        @Override
        public Drawable newDrawable() {
            return new VectorDrawable(null, null);
        }
        @Override
        public Drawable newDrawable(Resources res) {
            return new VectorDrawable(null, null);
        }
        @Override
        public Drawable newDrawable(Resources res, Theme theme) {
            return new VectorDrawable(null, null);
        }
        @Override
        public int getChangingConfigurations() {
            return mChangingConfigurations;
        }
    }
    private static class VPathRenderer {
        private final Path mPath = new Path();
        private final Path mRenderPath = new Path();
        private final Matrix mMatrix = new Matrix();
        private VPath[] mCurrentPaths;
        private Paint mStrokePaint;
        private Paint mFillPaint;
        private PathMeasure mPathMeasure;
        private VGroup mCurrentGroup = new VGroup();
        float mBaseWidth = 1;
        float mBaseHeight = 1;
        float mViewportWidth;
        float mViewportHeight;

        public VPathRenderer() {
        }

        public VPathRenderer(VPathRenderer copy) {
            mCurrentGroup = copy.mCurrentGroup;
            if (copy.mCurrentPaths != null) {
                mCurrentPaths = new VPath[copy.mCurrentPaths.length];
                for (int i = 0; i < mCurrentPaths.length; i++) {
                    mCurrentPaths[i] = new VPath(copy.mCurrentPaths[i]);
                }
            }
            mBaseWidth = copy.mBaseWidth;
            mBaseHeight = copy.mBaseHeight;
            mViewportWidth = copy.mViewportHeight;
            mViewportHeight = copy.mViewportHeight;
        }
        public boolean canApplyTheme() {
            final ArrayList<VPath> paths = mCurrentGroup.mVGList;
            for (int j = paths.size() - 1; j >= 0; j--) {
                final VPath path = paths.get(j);
                if (path.canApplyTheme()) {
                    return true;
                }
            }
            return false;
        }
        public void applyTheme(Theme t) {
            final ArrayList<VPath> paths = mCurrentGroup.mVGList;
            for (int j = paths.size() - 1; j >= 0; j--) {
                final VPath path = paths.get(j);
                if (path.canApplyTheme()) {
                    path.applyTheme(t);
                }
            }
        }
        public void draw(Canvas canvas, int w, int h) {
            if (mCurrentPaths == null) {
                Log.e(LOGTAG,"mCurrentPaths == null");
                return;
            }
            for (int i = 0; i < mCurrentPaths.length; i++) {
                if (mCurrentPaths[i] != null) {
                    drawPath(mCurrentPaths[i], canvas, w, h);
                }
            }
        }
        private void drawPath(VPath vPath, Canvas canvas, int w, int h) {
            final float scale = Math.min(h / mViewportHeight, w / mViewportWidth);
            vPath.toPath(mPath);
            final Path path = mPath;
            if (vPath.mTrimPathStart != 0.0f || vPath.mTrimPathEnd != 1.0f) {
                float start = (vPath.mTrimPathStart + vPath.mTrimPathOffset) % 1.0f;
                float end = (vPath.mTrimPathEnd + vPath.mTrimPathOffset) % 1.0f;
                if (mPathMeasure == null) {
                    mPathMeasure = new PathMeasure();
                }
                mPathMeasure.setPath(mPath, false);
                float len = mPathMeasure.getLength();
                start = start * len;
                end = end * len;
                path.reset();
                if (start > end) {
                    mPathMeasure.getSegment(start, len, path, true);
                    mPathMeasure.getSegment(0f, end, path, true);
                } else {
                    mPathMeasure.getSegment(start, end, path, true);
                }
                path.rLineTo(0, 0); // fix bug in measure
            }
            mRenderPath.reset();
            mMatrix.reset();
            mMatrix.postRotate(vPath.mRotate, vPath.mPivotX, vPath.mPivotY);
            mMatrix.postScale(scale, scale, mViewportWidth / 2f, mViewportHeight / 2f);
            mMatrix.postTranslate(w / 2f - mViewportWidth / 2f, h / 2f - mViewportHeight / 2f);
            mRenderPath.addPath(path, mMatrix);
            if (vPath.mClip) {
                canvas.clipPath(mRenderPath, Region.Op.REPLACE);
            }
            if (vPath.mFillColor != 0) {
                if (mFillPaint == null) {
                    mFillPaint = new Paint();
                    mFillPaint.setStyle(Paint.Style.FILL);
                    mFillPaint.setAntiAlias(true);
                }
                mFillPaint.setColor(vPath.mFillColor);
                canvas.drawPath(mRenderPath, mFillPaint);
            }
            if (vPath.mStrokeColor != 0) {
                if (mStrokePaint == null) {
                    mStrokePaint = new Paint();
                    mStrokePaint.setStyle(Paint.Style.STROKE);
                    mStrokePaint.setAntiAlias(true);
                }
                final Paint strokePaint = mStrokePaint;
                if (vPath.mStrokeLineJoin != null) {
                    strokePaint.setStrokeJoin(vPath.mStrokeLineJoin);
                }
                if (vPath.mStrokeLineCap != null) {
                    strokePaint.setStrokeCap(vPath.mStrokeLineCap);
                }
                strokePaint.setStrokeMiter(vPath.mStrokeMiterlimit * scale);
                strokePaint.setColor(vPath.mStrokeColor);
                strokePaint.setStrokeWidth(vPath.mStrokeWidth * scale);
                canvas.drawPath(mRenderPath, strokePaint);
            }
        }
        /**
         * Build the "current" path based on the current group
         * TODO: improve memory use & performance or move to C++
         */
        public void parseFinish() {
            final Collection<VPath> paths = mCurrentGroup.getPaths();
            mCurrentPaths = paths.toArray(new VPath[paths.size()]);
            for (int i = 0; i < mCurrentPaths.length; i++) {
                mCurrentPaths[i] = new VPath(mCurrentPaths[i]);
            }
        }
        private void parseViewport(Resources r, AttributeSet attrs)
                throws XmlPullParserException {
//            final TypedArray a = r.obtainAttributes(attrs, R.styleable.VectorDrawableViewport);
//            mViewportWidth = a.getFloat(R.styleable.VectorDrawableViewport_viewportWidth, 0);
//            mViewportHeight = a.getFloat(R.styleable.VectorDrawableViewport_viewportHeight, 0);
//            if (mViewportWidth == 0 || mViewportHeight == 0) {
//                throw new XmlPullParserException(a.getPositionDescription()+
//                        "<viewport> tag requires viewportWidth & viewportHeight to be set");
//            }
//            a.recycle();
        }
        private void parseSize(Resources r, AttributeSet attrs)
                throws XmlPullParserException  {
//            final TypedArray a = r.obtainAttributes(attrs, R.styleable.VectorDrawableSize);
//            mBaseWidth = a.getDimension(R.styleable.VectorDrawableSize_width, 0);
//            mBaseHeight = a.getDimension(R.styleable.VectorDrawableSize_height, 0);
//            if (mBaseWidth == 0 || mBaseHeight == 0) {
//                throw new XmlPullParserException(a.getPositionDescription()+
//                        "<size> tag requires width & height to be set");
//            }
//            a.recycle();
        }

        private void parseSize(ObjectValue value) {

        }
    }
    private static class VPath {
        private static final int MAX_STATES = 10;
        private int[] mThemeAttrs;
        int mStrokeColor = 0;
        float mStrokeWidth = 0;
        float mStrokeOpacity = Float.NaN;
        int mFillColor = 0;
        int mFillRule;
        float mFillOpacity = Float.NaN;
        float mRotate = 0;
        float mPivotX = 0;
        float mPivotY = 0;
        float mTrimPathStart = 0;
        float mTrimPathEnd = 1;
        float mTrimPathOffset = 0;
        boolean mClip = false;
        Paint.Cap mStrokeLineCap = Paint.Cap.BUTT;
        Paint.Join mStrokeLineJoin = Paint.Join.MITER;
        float mStrokeMiterlimit = 4;
        private VNode[] mNode = null;
        private String mId;
        private int[] mCheckState = new int[MAX_STATES];
        private boolean[] mCheckValue = new boolean[MAX_STATES];
        private int mNumberOfStates = 0;
        public VPath() {
            // Empty constructor.
        }
        public VPath(VPath p) {
            copyFrom(p);
        }
        public void toPath(Path path) {
            path.reset();
            if (mNode != null) {
                VNode.createPath(mNode, path);
            }
        }
        public String getID() {
            return mId;
        }
        private Paint.Cap getStrokeLineCap(int id, Paint.Cap defValue) {
            switch (id) {
                case LINECAP_BUTT:
                    return Paint.Cap.BUTT;
                case LINECAP_ROUND:
                    return Paint.Cap.ROUND;
                case LINECAP_SQUARE:
                    return Paint.Cap.SQUARE;
                default:
                    return defValue;
            }
        }
        private Paint.Join getStrokeLineJoin(int id, Paint.Join defValue) {
            switch (id) {
                case LINEJOIN_MITER:
                    return Paint.Join.MITER;
                case LINEJOIN_ROUND:
                    return Paint.Join.ROUND;
                case LINEJOIN_BEVEL:
                    return Paint.Join.BEVEL;
                default:
                    return defValue;
            }
        }

        public void inflate(ObjectValue values) {
            mClip = values.getAsBoolean("clipToPath", false);

            mId = "nameasdasd";

            mNode = parsePath(values.getAsString("pathData"));

            mFillColor = 0xffFE6262;
            mStrokeColor = 0xffFE6262;
            mStrokeWidth = 20;

            updateColorAlphas();
        }

        public void inflate(Resources r, AttributeSet attrs, Theme theme) {
            final TypedArray a = null;//obtainAttributes(r, theme, attrs, R.styleable.VectorDrawablePath);
           // final int[] themeAttrs = a.extractThemeAttrs();
           // mThemeAttrs = themeAttrs;
            // NOTE: The set of attributes loaded here MUST match the
            // set of attributes loaded in applyTheme.
//            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_clipToPath] == 0) {
//                mClip = a.getBoolean(R.styleable.VectorDrawablePath_clipToPath, mClip);
//            }
//            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_name] == 0) {
//                mId = a.getString(R.styleable.VectorDrawablePath_name);
//            }
//            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_pathData] == 0) {
//                mNode = parsePath(a.getString(R.styleable.VectorDrawablePath_pathData));
//            }
//            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_fill] == 0) {
//                mFillColor = a.getColor(R.styleable.VectorDrawablePath_fill, mFillColor);
//            }
//            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_fillOpacity] == 0) {
//                mFillOpacity = a.getFloat(R.styleable.VectorDrawablePath_fillOpacity, mFillOpacity);
//            }
//            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_rotation] == 0) {
//                mRotate = a.getFloat(R.styleable.VectorDrawablePath_rotation, mRotate);
//            }
//            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_pivotX] == 0) {
//                mPivotX = a.getFloat(R.styleable.VectorDrawablePath_pivotX, mPivotX);
//            }
//            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_pivotY] == 0) {
//                mPivotY = a.getFloat(R.styleable.VectorDrawablePath_pivotY, mPivotY);
//            }
//            if (themeAttrs == null
//                    || themeAttrs[R.styleable.VectorDrawablePath_strokeLineCap] == 0) {
//                mStrokeLineCap = getStrokeLineCap(
//                        a.getInt(R.styleable.VectorDrawablePath_strokeLineCap, -1), mStrokeLineCap);
//            }
//            if (themeAttrs == null
//                    || themeAttrs[R.styleable.VectorDrawablePath_strokeLineJoin] == 0) {
//                mStrokeLineJoin = getStrokeLineJoin(
//                        a.getInt(R.styleable.VectorDrawablePath_strokeLineJoin, -1), mStrokeLineJoin);
//            }
//            if (themeAttrs == null
//                    || themeAttrs[R.styleable.VectorDrawablePath_strokeMiterLimit] == 0) {
//                mStrokeMiterlimit = a.getFloat(
//                        R.styleable.VectorDrawablePath_strokeMiterLimit, mStrokeMiterlimit);
//            }
//            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_stroke] == 0) {
//                mStrokeColor = a.getColor(R.styleable.VectorDrawablePath_stroke, mStrokeColor);
//            }
//            if (themeAttrs == null
//                    || themeAttrs[R.styleable.VectorDrawablePath_strokeOpacity] == 0) {
//                mStrokeOpacity = a.getFloat(
//                        R.styleable.VectorDrawablePath_strokeOpacity, mStrokeOpacity);
//            }
//            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_strokeWidth] == 0) {
//                mStrokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth, mStrokeWidth);
//            }
//            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_trimPathEnd] == 0) {
//                mTrimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd, mTrimPathEnd);
//            }
//            if (themeAttrs == null
//                    || themeAttrs[R.styleable.VectorDrawablePath_trimPathOffset] == 0) {
//                mTrimPathOffset = a.getFloat(
//                        R.styleable.VectorDrawablePath_trimPathOffset, mTrimPathOffset);
//            }
//            if (themeAttrs == null
//                    || themeAttrs[R.styleable.VectorDrawablePath_trimPathStart] == 0) {
//                mTrimPathStart = a.getFloat(
//                        R.styleable.VectorDrawablePath_trimPathStart, mTrimPathStart);
//            }
            updateColorAlphas();
            a.recycle();
        }
        public boolean canApplyTheme() {
            return mThemeAttrs != null;
        }
        public void applyTheme(Theme t) {
            if (mThemeAttrs == null) {
                return;
            }
//            final TypedArray a = t.resolveAttributes(
//                    mThemeAttrs, R.styleable.VectorDrawablePath);
//            mClip = a.getBoolean(R.styleable.VectorDrawablePath_clipToPath, mClip);
//            if (a.hasValue(R.styleable.VectorDrawablePath_name)) {
//                mId = a.getString(R.styleable.VectorDrawablePath_name);
//            }
//            if (a.hasValue(R.styleable.VectorDrawablePath_pathData)) {
//                mNode = parsePath(a.getString(R.styleable.VectorDrawablePath_pathData));
//            }
//            mFillColor = a.getColor(R.styleable.VectorDrawablePath_fill, mFillColor);
//            mFillOpacity = a.getFloat(R.styleable.VectorDrawablePath_fillOpacity, mFillOpacity);
//            mRotate = a.getFloat(R.styleable.VectorDrawablePath_rotation, mRotate);
//            mPivotX = a.getFloat(R.styleable.VectorDrawablePath_pivotX, mPivotX);
//            mPivotY = a.getFloat(R.styleable.VectorDrawablePath_pivotY, mPivotY);
//            mStrokeLineCap = getStrokeLineCap(a.getInt(
//                    R.styleable.VectorDrawablePath_strokeLineCap, -1), mStrokeLineCap);
//            mStrokeLineJoin = getStrokeLineJoin(a.getInt(
//                    R.styleable.VectorDrawablePath_strokeLineJoin, -1), mStrokeLineJoin);
//            mStrokeMiterlimit = a.getFloat(
//                    R.styleable.VectorDrawablePath_strokeMiterLimit, mStrokeMiterlimit);
//            mStrokeColor = a.getColor(R.styleable.VectorDrawablePath_stroke, mStrokeColor);
//            mStrokeOpacity = a.getFloat(
//                    R.styleable.VectorDrawablePath_strokeOpacity, mStrokeOpacity);
//            mStrokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth, mStrokeWidth);
//            mTrimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd, mTrimPathEnd);
//            mTrimPathOffset = a.getFloat(
//                    R.styleable.VectorDrawablePath_trimPathOffset, mTrimPathOffset);
//            mTrimPathStart = a.getFloat(
//                    R.styleable.VectorDrawablePath_trimPathStart, mTrimPathStart);
            updateColorAlphas();
        }
        private void updateColorAlphas() {
            if (!Float.isNaN(mFillOpacity)) {
                mFillColor &= 0x00FFFFFF;
                mFillColor |= ((int) (0xFF * mFillOpacity)) << 24;
            }
            if (!Float.isNaN(mStrokeOpacity)) {
                mStrokeColor &= 0x00FFFFFF;
                mStrokeColor |= ((int) (0xFF * mStrokeOpacity)) << 24;
            }
        }
        private static int nextStart(String s, int end) {
            char c;
            while (end < s.length()) {
                c = s.charAt(end);
                if (((c - 'A') * (c - 'Z') <= 0) || (((c - 'a') * (c - 'z') <= 0))) {
                    return end;
                }
                end++;
            }
            return end;
        }
        private void addNode(ArrayList<VNode> list, char cmd, float[] val) {
            list.add(new VectorDrawable.VNode(cmd, val));
        }
        /**
         * parse the floats in the string
         * this is an optimized version of
         * parseFloat(s.split(",|\\s"));
         *
         * @param s the string containing a command and list of floats
         * @return array of floats
         */
        private static float[] getFloats(String s) {
            if (s.charAt(0) == 'z' | s.charAt(0) == 'Z') {
                return new float[0];
            }
            try {
                float[] tmp = new float[s.length()];
                int count = 0;
                int pos = 1, end;
                while ((end = extract(s, pos)) >= 0) {
                    if (pos < end) {
                        tmp[count++] = Float.parseFloat(s.substring(pos, end));
                    }
                    pos = end + 1;
                }
                // handle the final float if there is one
                if (pos < s.length()) {
                    tmp[count++] = Float.parseFloat(s.substring(pos, s.length()));
                }
                return Arrays.copyOf(tmp, count);
            } catch (NumberFormatException e){
                Log.e(LOGTAG,"error in parsing \""+s+"\"");
                throw e;
            }
        }
        /**
         * calculate the position of the next comma or space
         * @param s the string to search
         * @param start the position to start searching
         * @return the position of the next comma or space or -1 if none found
         */
        private static int extract(String s, int start) {
            int space = s.indexOf(' ', start);
            int comma = s.indexOf(',', start);
            if (space == -1) {
                return comma;
            }
            if (comma == -1) {
                return space;
            }
            return (comma > space) ? space : comma;
        }
        private VectorDrawable.VNode[] parsePath(String value) {
            int start = 0;
            int end = 1;
            ArrayList<VectorDrawable.VNode> list = new ArrayList<>();
            while (end < value.length()) {
                end = nextStart(value, end);
                String s = value.substring(start, end);
                float[] val = getFloats(s);
                addNode(list, s.charAt(0), val);
                start = end;
                end++;
            }
            if ((end - start) == 1 && start < value.length()) {
                addNode(list, value.charAt(start), new float[0]);
            }
            return list.toArray(new VectorDrawable.VNode[list.size()]);
        }
        public void copyFrom(VPath p1) {
            mNode = new VNode[p1.mNode.length];
            for (int i = 0; i < mNode.length; i++) {
                mNode[i] = new VNode(p1.mNode[i]);
            }
            mId = p1.mId;
            mStrokeColor = p1.mStrokeColor;
            mFillColor = p1.mFillColor;
            mStrokeWidth = p1.mStrokeWidth;
            mRotate = p1.mRotate;
            mPivotX = p1.mPivotX;
            mPivotY = p1.mPivotY;
            mTrimPathStart = p1.mTrimPathStart;
            mTrimPathEnd = p1.mTrimPathEnd;
            mTrimPathOffset = p1.mTrimPathOffset;
            mStrokeLineCap = p1.mStrokeLineCap;
            mStrokeLineJoin = p1.mStrokeLineJoin;
            mStrokeMiterlimit = p1.mStrokeMiterlimit;
            mNumberOfStates = p1.mNumberOfStates;
            for (int i = 0; i < mNumberOfStates; i++) {
                mCheckState[i] = p1.mCheckState[i];
                mCheckValue[i] = p1.mCheckValue[i];
            }
            mFillRule = p1.mFillRule;
        }
    }
    private static class VGroup {
        private final HashMap<String, VPath> mVGPathMap = new HashMap<String, VPath>();
        private final ArrayList<VPath> mVGList = new ArrayList<VPath>();
        public void add(VPath path) {
            String id = path.getID();
            mVGPathMap.put(id, path);
            mVGList.add(path);
        }
        /**
         * Must return in order of adding
         * @return ordered list of paths
         */
        public Collection<VPath> getPaths() {
            return mVGList;
        }
    }
    private static class VNode {
        private char mType;
        private float[] mParams;
        public VNode(char type, float[] params) {
            mType = type;
            mParams = params;
        }
        public VNode(VNode n) {
            mType = n.mType;
            mParams = Arrays.copyOf(n.mParams, n.mParams.length);
        }
        public static void createPath(VNode[] node, Path path) {
            float[] current = new float[4];
            char previousCommand = 'm';
            for (int i = 0; i < node.length; i++) {
                addCommand(path, current, previousCommand, node[i].mType, node[i].mParams);
                previousCommand = node[i].mType;
            }
        }
        private static void addCommand(Path path, float[] current,
                                       char previousCmd, char cmd, float[] val) {
            int incr = 2;
            float currentX = current[0];
            float currentY = current[1];
            float ctrlPointX = current[2];
            float ctrlPointY = current[3];
            float reflectiveCtrlPointX;
            float reflectiveCtrlPointY;
            switch (cmd) {
                case 'z':
                case 'Z':
                    path.close();
                    return;
                case 'm':
                case 'M':
                case 'l':
                case 'L':
                case 't':
                case 'T':
                    incr = 2;
                    break;
                case 'h':
                case 'H':
                case 'v':
                case 'V':
                    incr = 1;
                    break;
                case 'c':
                case 'C':
                    incr = 6;
                    break;
                case 's':
                case 'S':
                case 'q':
                case 'Q':
                    incr = 4;
                    break;
                case 'a':
                case 'A':
                    incr = 7;
                    break;
            }
            for (int k = 0; k < val.length; k += incr) {
                switch (cmd) {
                    case 'm': // moveto - Start a new sub-path (relative)
                        path.rMoveTo(val[k + 0], val[k + 1]);
                        currentX += val[k + 0];
                        currentY += val[k + 1];
                        break;
                    case 'M': // moveto - Start a new sub-path
                        path.moveTo(val[k + 0], val[k + 1]);
                        currentX = val[k + 0];
                        currentY = val[k + 1];
                        break;
                    case 'l': // lineto - Draw a line from the current point (relative)
                        path.rLineTo(val[k + 0], val[k + 1]);
                        currentX += val[k + 0];
                        currentY += val[k + 1];
                        break;
                    case 'L': // lineto - Draw a line from the current point
                        path.lineTo(val[k + 0], val[k + 1]);
                        currentX = val[k + 0];
                        currentY = val[k + 1];
                        break;
                    case 'z': // closepath - Close the current subpath
                    case 'Z': // closepath - Close the current subpath
                        path.close();
                        break;
                    case 'h': // horizontal lineto - Draws a horizontal line (relative)
                        path.rLineTo(val[k + 0], 0);
                        currentX += val[k + 0];
                        break;
                    case 'H': // horizontal lineto - Draws a horizontal line
                        path.lineTo(val[k + 0], currentY);
                        currentX = val[k + 0];
                        break;
                    case 'v': // vertical lineto - Draws a vertical line from the current point (r)
                        path.rLineTo(0, val[k + 0]);
                        currentY += val[k + 0];
                        break;
                    case 'V': // vertical lineto - Draws a vertical line from the current point
                        path.lineTo(currentX, val[k + 0]);
                        currentY = val[k + 0];
                        break;
                    case 'c': // curveto - Draws a cubic Bézier curve (relative)
                        path.rCubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
                                val[k + 4], val[k + 5]);
                        ctrlPointX = currentX + val[k + 2];
                        ctrlPointY = currentY + val[k + 3];
                        currentX += val[k + 4];
                        currentY += val[k + 5];
                        break;
                    case 'C': // curveto - Draws a cubic Bézier curve
                        path.cubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
                                val[k + 4], val[k + 5]);
                        currentX = val[k + 4];
                        currentY = val[k + 5];
                        ctrlPointX = val[k + 2];
                        ctrlPointY = val[k + 3];
                        break;
                    case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp)
                        reflectiveCtrlPointX = 0;
                        reflectiveCtrlPointY = 0;
                        if (previousCmd == 'c' || previousCmd == 's'
                                || previousCmd == 'C' || previousCmd == 'S') {
                            reflectiveCtrlPointX = currentX - ctrlPointX;
                            reflectiveCtrlPointY = currentY - ctrlPointY;
                        }
                        path.rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
                                val[k + 0], val[k + 1],
                                val[k + 2], val[k + 3]);
                        ctrlPointX = currentX + val[k + 0];
                        ctrlPointY = currentY + val[k + 1];
                        currentX += val[k + 2];
                        currentY += val[k + 3];
                        break;
                    case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp)
                        reflectiveCtrlPointX = currentX;
                        reflectiveCtrlPointY = currentY;
                        if (previousCmd == 'c' || previousCmd == 's'
                                || previousCmd == 'C' || previousCmd == 'S') {
                            reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
                            reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
                        }
                        path.cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
                                val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
                        ctrlPointX = val[k + 0];
                        ctrlPointY = val[k + 1];
                        currentX = val[k + 2];
                        currentY = val[k + 3];
                        break;
                    case 'q': // Draws a quadratic Bézier (relative)
                        path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
                        ctrlPointX = currentX + val[k + 0];
                        ctrlPointY = currentY + val[k + 1];
                        currentX += val[k + 2];
                        currentY += val[k + 3];
                        break;
                    case 'Q': // Draws a quadratic Bézier
                        path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
                        ctrlPointX = val[k + 0];
                        ctrlPointY = val[k + 1];
                        currentX = val[k + 2];
                        currentY = val[k + 3];
                        break;
                    case 't': // Draws a quadratic Bézier curve(reflective control point)(relative)
                        reflectiveCtrlPointX = 0;
                        reflectiveCtrlPointY = 0;
                        if (previousCmd == 'q' || previousCmd == 't'
                                || previousCmd == 'Q' || previousCmd == 'T') {
                            reflectiveCtrlPointX = currentX - ctrlPointX;
                            reflectiveCtrlPointY = currentY - ctrlPointY;
                        }
                        path.rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
                                val[k + 0], val[k + 1]);
                        ctrlPointX = currentX + reflectiveCtrlPointX;
                        ctrlPointY = currentY + reflectiveCtrlPointY;
                        currentX += val[k + 0];
                        currentY += val[k + 1];
                        break;
                    case 'T': // Draws a quadratic Bézier curve (reflective control point)
                        reflectiveCtrlPointX = currentX;
                        reflectiveCtrlPointY = currentY;
                        if (previousCmd == 'q' || previousCmd == 't'
                                || previousCmd == 'Q' || previousCmd == 'T') {
                            reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
                            reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
                        }
                        path.quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
                                val[k + 0], val[k + 1]);
                        ctrlPointX = reflectiveCtrlPointX;
                        ctrlPointY = reflectiveCtrlPointY;
                        currentX = val[k + 0];
                        currentY = val[k + 1];
                        break;
                    case 'a': // Draws an elliptical arc
                        // (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
                        drawArc(path,
                                currentX,
                                currentY,
                                val[k + 5] + currentX,
                                val[k + 6] + currentY,
                                val[k + 0],
                                val[k + 1],
                                val[k + 2],
                                val[k + 3] != 0,
                                val[k + 4] != 0);
                        currentX += val[k + 5];
                        currentY += val[k + 6];
                        ctrlPointX = currentX;
                        ctrlPointY = currentY;
                        break;
                    case 'A': // Draws an elliptical arc
                        drawArc(path,
                                currentX,
                                currentY,
                                val[k + 5],
                                val[k + 6],
                                val[k + 0],
                                val[k + 1],
                                val[k + 2],
                                val[k + 3] != 0,
                                val[k + 4] != 0);
                        currentX = val[k + 5];
                        currentY = val[k + 6];
                        ctrlPointX = currentX;
                        ctrlPointY = currentY;
                        break;
                }
                previousCmd = cmd;
            }
            current[0] = currentX;
            current[1] = currentY;
            current[2] = ctrlPointX;
            current[3] = ctrlPointY;
        }
        private static void drawArc(Path p,
                                    float x0,
                                    float y0,
                                    float x1,
                                    float y1,
                                    float a,
                                    float b,
                                    float theta,
                                    boolean isMoreThanHalf,
                                    boolean isPositiveArc) {
            /* Convert rotation angle from degrees to radians */
            double thetaD = Math.toRadians(theta);
            /* Pre-compute rotation matrix entries */
            double cosTheta = Math.cos(thetaD);
            double sinTheta = Math.sin(thetaD);
            /* Transform (x0, y0) and (x1, y1) into unit space */
            /* using (inverse) rotation, followed by (inverse) scale */
            double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
            double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
            double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
            double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
            /* Compute differences and averages */
            double dx = x0p - x1p;
            double dy = y0p - y1p;
            double xm = (x0p + x1p) / 2;
            double ym = (y0p + y1p) / 2;
            /* Solve for intersecting unit circles */
            double dsq = dx * dx + dy * dy;
            if (dsq == 0.0) {
                Log.w("VectorDrawable", " Points are coincident");
                return; /* Points are coincident */
            }
            double disc = 1.0 / dsq - 1.0 / 4.0;
            if (disc < 0.0) {
                Log.w("VectorDrawable", "Points are too far apart " + dsq);
                float adjust = (float) (Math.sqrt(dsq) / 1.99999);
                drawArc(p, x0, y0, x1, y1, a * adjust,
                        b * adjust, theta, isMoreThanHalf, isPositiveArc);
                return; /* Points are too far apart */
            }
            double s = Math.sqrt(disc);
            double sdx = s * dx;
            double sdy = s * dy;
            double cx;
            double cy;
            if (isMoreThanHalf == isPositiveArc) {
                cx = xm - sdy;
                cy = ym + sdx;
            } else {
                cx = xm + sdy;
                cy = ym - sdx;
            }
            double eta0 = Math.atan2((y0p - cy), (x0p - cx));
            double eta1 = Math.atan2((y1p - cy), (x1p - cx));
            double sweep = (eta1 - eta0);
            if (isPositiveArc != (sweep >= 0)) {
                if (sweep > 0) {
                    sweep -= 2 * Math.PI;
                } else {
                    sweep += 2 * Math.PI;
                }
            }
            cx *= a;
            cy *= b;
            double tcx = cx;
            cx = cx * cosTheta - cy * sinTheta;
            cy = tcx * sinTheta + cy * cosTheta;
            arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
        }
        /**
         * Converts an arc to cubic Bezier segments and records them in p.
         *
         * @param p The target for the cubic Bezier segments
         * @param cx The x coordinate center of the ellipse
         * @param cy The y coordinate center of the ellipse
         * @param a The radius of the ellipse in the horizontal direction
         * @param b The radius of the ellipse in the vertical direction
         * @param e1x E(eta1) x coordinate of the starting point of the arc
         * @param e1y E(eta2) y coordinate of the starting point of the arc
         * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane
         * @param start The start angle of the arc on the ellipse
         * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
         */
        private static void arcToBezier(Path p,
                                        double cx,
                                        double cy,
                                        double a,
                                        double b,
                                        double e1x,
                                        double e1y,
                                        double theta,
                                        double start,
                                        double sweep) {
            // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html
            // and http://www.spaceroots.org/documents/ellipse/node22.html
            // Maximum of 45 degrees per cubic Bezier segment
            int numSegments = Math.abs((int) Math.ceil(sweep * 4 / Math.PI));
            double eta1 = start;
            double cosTheta = Math.cos(theta);
            double sinTheta = Math.sin(theta);
            double cosEta1 = Math.cos(eta1);
            double sinEta1 = Math.sin(eta1);
            double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
            double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
            double anglePerSegment = sweep / numSegments;
            for (int i = 0; i < numSegments; i++) {
                double eta2 = eta1 + anglePerSegment;
                double sinEta2 = Math.sin(eta2);
                double cosEta2 = Math.cos(eta2);
                double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2);
                double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2);
                double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
                double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
                double tanDiff2 = Math.tan((eta2 - eta1) / 2);
                double alpha =
                        Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
                double q1x = e1x + alpha * ep1x;
                double q1y = e1y + alpha * ep1y;
                double q2x = e2x - alpha * ep2x;
                double q2y = e2y - alpha * ep2y;
                p.cubicTo((float) q1x,
                        (float) q1y,
                        (float) q2x,
                        (float) q2y,
                        (float) e2x,
                        (float) e2y);
                eta1 = eta2;
                e1x = e2x;
                e1y = e2y;
                ep1x = ep2x;
                ep1y = ep2y;
            }
        }
    }
}
